﻿' Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

Imports System.Collections.Immutable
Imports Microsoft.CodeAnalysis.CodeGen
Imports Microsoft.CodeAnalysis.ExpressionEvaluator
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
Imports Microsoft.VisualStudio.SymReaderInterop
Imports Xunit

Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests

    Public Class StaticLocalsTests
        Inherits ExpressionCompilerTestBase

        <Fact>
        Public Sub StaticLocals()
            Const source =
"Class C
    Shared Function F(b As Boolean) As Object
        If b Then
            Static x As New C()
            Return x
        Else
            Static y = 2
            Return y
        End If
    End Function
    Sub M()
        Static x = Nothing
    End Sub
End Class"
            Dim comp = CreateCompilationWithMscorlib({source}, {MsvbRef}, compOptions:=TestOptions.DebugDll)
            Dim runtime = CreateRuntimeInstance(comp)
            ' Shared method.
            Dim context = CreateMethodContext(runtime, "C.F")
            Dim errorMessage As String = Nothing
            Dim testData = New CompilationTestData()
            context.CompileExpression("If(x, y)", errorMessage, testData)
            testData.GetMethodData("<>x.<>m0").VerifyIL(
"{
  // Code size       15 (0xf)
  .maxstack  2
  .locals init (Object V_0, //F
                Boolean V_1,
                Boolean V_2,
                Boolean V_3)
  IL_0000:  ldsfld     ""C.$STATIC$F$011C2$x As C""
  IL_0005:  dup
  IL_0006:  brtrue.s   IL_000e
  IL_0008:  pop
  IL_0009:  ldsfld     ""C.$STATIC$F$011C2$y As Object""
  IL_000e:  ret
}")
            ' Instance method.
            context = CreateMethodContext(runtime, "C.M")
            testData = New CompilationTestData()
            context.CompileExpression("If(x, Me)", errorMessage, testData)
            testData.GetMethodData("<>x.<>m0").VerifyIL(
"{
  // Code size       12 (0xc)
  .maxstack  2
  .locals init (Boolean V_0,
                Boolean V_1)
  IL_0000:  ldarg.0
  IL_0001:  ldfld      ""C.$STATIC$M$2001$x As Object""
  IL_0006:  dup
  IL_0007:  brtrue.s   IL_000b
  IL_0009:  pop
  IL_000a:  ldarg.0
  IL_000b:  ret
}")
        End Sub

        <Fact>
        Public Sub AssignStaticLocals()
            Const source =
"Class C
    Shared Sub M()
        Static x = Nothing
        Static y = 1
    End Sub
    Sub N()
        Static z As New C()
    End Sub
End Class"
            Dim comp = CreateCompilationWithMscorlib({source}, {MsvbRef}, compOptions:=TestOptions.DebugDll)
            Dim runtime = CreateRuntimeInstance(comp)
            ' Shared method.
            Dim context = CreateMethodContext(runtime, "C.M")
            Dim errorMessage As String = Nothing
            Dim testData = New CompilationTestData()
            context.CompileAssignment("x", "y", errorMessage, testData)
            testData.GetMethodData("<>x.<>m0").VerifyIL(
"{
  // Code size       16 (0x10)
  .maxstack  1
  .locals init (Boolean V_0,
                Boolean V_1,
                Boolean V_2)
  IL_0000:  ldsfld     ""C.$STATIC$M$001$y As Object""
  IL_0005:  call       ""Function System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(Object) As Object""
  IL_000a:  stsfld     ""C.$STATIC$M$001$x As Object""
  IL_000f:  ret
}")
            ' Instance method.
            context = CreateMethodContext(runtime, "C.N")
            testData = New CompilationTestData()
            context.CompileAssignment("z", "Nothing", errorMessage, testData)
            testData.GetMethodData("<>x.<>m0").VerifyIL(
"{
  // Code size        8 (0x8)
  .maxstack  2
  .locals init (Boolean V_0,
                Boolean V_1)
  IL_0000:  ldarg.0
  IL_0001:  ldnull
  IL_0002:  stfld      ""C.$STATIC$N$2001$z As C""
  IL_0007:  ret
}")
        End Sub

        ''' <summary>
        ''' Static locals not exposed in the EE from lambdas.
        ''' This matches Dev12 EE behavior since there is no
        ''' map from the lambda to the containing method.
        ''' </summary>
        <Fact>
        Public Sub StaticLocalsReferenceInLambda()
            Const source =
"Class C
    Sub M(x As Object)
        Static y As Object
        Dim f = Function()
                    Return If(x, y)
                End Function
        f()
    End Sub
    Shared Sub M(x As Integer)
        Static z As Integer
        Dim f = Function()
                    Return x + z
                End Function
        f()
    End Sub
End Class"
            Dim comp = CreateCompilationWithMscorlib({source}, {MsvbRef}, compOptions:=TestOptions.DebugDll)
            Dim runtime = CreateRuntimeInstance(comp)
            ' Instance method.
            Dim context = CreateMethodContext(runtime, "C._Closure$__1-0._Lambda$__1")
            Dim errorMessage As String = Nothing
            Dim testData = New CompilationTestData()
            context.CompileExpression("If(x, y)", errorMessage, testData)
            Assert.Equal(errorMessage, "(1,8): error BC30451: 'y' is not declared. It may be inaccessible due to its protection level.")
            ' Shared method.
            context = CreateMethodContext(runtime, "C._Closure$__2-0._Lambda$__1")
            testData = New CompilationTestData()
            context.CompileExpression("x + z", errorMessage, testData)
            Assert.Equal(errorMessage, "(1,6): error BC30451: 'z' is not declared. It may be inaccessible due to its protection level.")
        End Sub

        <Fact>
        Public Sub GetLocals()
            Const source =
"Class C
    Shared Function F(b As Boolean) As Object
        If b Then
            Static x As New C()
            Return x
        Else
            Static y As Integer = 2
            Return y
        End If
    End Function
    Shared Function F(i As Integer) As Object
        Static x = 3
        Return x
    End Function
End Class"
            Dim comp = CreateCompilationWithMscorlib({source}, {MsvbRef}, compOptions:=TestOptions.DebugDll)
            Dim runtime = CreateRuntimeInstance(comp)
            Dim blocks As ImmutableArray(Of MetadataBlock) = Nothing
            Dim moduleVersionId As Guid = Nothing
            Dim symReader As ISymUnmanagedReader = Nothing
            Dim methodToken = 0
            Dim localSignatureToken = 0
            GetContextState(runtime, "C.F(Boolean)", blocks, moduleVersionId, symReader, methodToken, localSignatureToken)
            Dim context = EvaluationContext.CreateMethodContext(
                Nothing,
                blocks,
                MakeDummyLazyAssemblyReaders(),
                symReader,
                moduleVersionId,
                methodToken,
                methodVersion:=1,
                ilOffset:=0,
                localSignatureToken:=localSignatureToken)
            Dim testData = New CompilationTestData()
            Dim locals = ArrayBuilder(Of LocalAndMethod).GetInstance()
            Dim typeName As String = Nothing
            Dim assembly = context.CompileGetLocals(locals, argumentsOnly:=False, typeName:=typeName, testData:=testData)
            Assert.Equal(4, locals.Count)
            VerifyLocal(testData, typeName, locals(0), "<>m0", "b")
            VerifyLocal(testData, typeName, locals(1), "<>m1", "F")
            VerifyLocal(testData, typeName, locals(2), "<>m2", "x", expectedILOpt:=
"{
  // Code size        6 (0x6)
  .maxstack  1
  .locals init (Object V_0, //F
                Boolean V_1,
                Boolean V_2,
                Boolean V_3)
  IL_0000:  ldsfld     ""C.$STATIC$F$011C2$x As C""
  IL_0005:  ret
}")
            VerifyLocal(testData, typeName, locals(3), "<>m3", "y", expectedILOpt:=
"{
  // Code size        6 (0x6)
  .maxstack  1
  .locals init (Object V_0, //F
                Boolean V_1,
                Boolean V_2,
                Boolean V_3)
  IL_0000:  ldsfld     ""C.$STATIC$F$011C2$y As Integer""
  IL_0005:  ret
}")
            locals.Free()

            GetContextState(runtime, "C.F(Integer)", blocks, moduleVersionId, symReader, methodToken, localSignatureToken)
            context = EvaluationContext.CreateMethodContext(
                Nothing,
                blocks,
                MakeDummyLazyAssemblyReaders(),
                symReader,
                moduleVersionId,
                methodToken,
                methodVersion:=1,
                ilOffset:=0,
                localSignatureToken:=localSignatureToken)
            testData = New CompilationTestData()
            locals = ArrayBuilder(Of LocalAndMethod).GetInstance()
            assembly = context.CompileGetLocals(locals, argumentsOnly:=False, typeName:=typeName, testData:=testData)
            Assert.Equal(3, locals.Count)
            VerifyLocal(testData, typeName, locals(0), "<>m0", "i")
            VerifyLocal(testData, typeName, locals(1), "<>m1", "F")
            VerifyLocal(testData, typeName, locals(2), "<>m2", "x", expectedILOpt:=
"{
  // Code size        6 (0x6)
  .maxstack  1
  .locals init (Object V_0, //F
                Boolean V_1,
                Boolean V_2)
  IL_0000:  ldsfld     ""C.$STATIC$F$011C8$x As Object""
  IL_0005:  ret
}")
            locals.Free()
        End Sub

        ''' <summary>
        ''' Static locals not exposed in the EE from lambdas.
        ''' This matches Dev12 EE behavior since there is no
        ''' map from the lambda to the containing method.
        ''' </summary>
        <Fact>
        Public Sub GetLocalsInLambda()
            Const source =
"Class C
    Sub M(x As Object)
        Static y As Object
        Dim f = Function()
                    Return If(x, y)
                End Function
        f()
    End Sub
End Class"
            Dim comp = CreateCompilationWithMscorlib({source}, {MsvbRef}, compOptions:=TestOptions.DebugDll)
            Dim runtime = CreateRuntimeInstance(comp)
            Dim context = CreateMethodContext(runtime, "C._Closure$__1-0._Lambda$__1")
            Dim testData = New CompilationTestData()
            Dim locals = ArrayBuilder(Of LocalAndMethod).GetInstance()
            Dim typeName As String = Nothing
            Dim assembly = context.CompileGetLocals(locals, argumentsOnly:=False, typeName:=typeName, testData:=testData)
            Assert.Equal(2, locals.Count)
            VerifyLocal(testData, typeName, locals(0), "<>m0", "Me")
            VerifyLocal(testData, typeName, locals(1), "<>m1", "x")
            locals.Free()
        End Sub

    End Class

End Namespace
